<!DOCTYPE html>


<html lang="zh-Hans">
  

    <head>
      <meta charset="utf-8" />
       
      <meta name="keywords" content="博客，代码，思考，生活" />
       
      <meta name="description" content="笔记汇总" />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title> Zhang Shuo&#39;s Studio</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/hexo1/favicon.ico" />
       
<link rel="stylesheet" href="/hexo1/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/hexo1/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/hexo1/atom.xml" title="Zhang Shuo's Studio" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      
<section class="cover">

  <div class="cover-frame">
    <div class="bg-box">
      <img src="/hexo1/images/cover1.jpg" alt="image frame" />
    </div>
    <div class="cover-inner text-center text-white">
      <h1><a href="/hexo1/">Zhang Shuo&#39;s Studio</a></h1>
      <div id="subtitle-box">
        
        <span id="subtitle"></span>
        
      </div>
      <div>
        
      </div>
    </div>
  </div>
  <div class="cover-learn-more">
    <a href="javascript:void(0)" class="anchor"><i class="ri-arrow-down-line"></i></a>
  </div>
</section>



<script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11/lib/typed.min.js"></script>


<!-- Subtitle -->

  <script>
    try {
      var typed = new Typed("#subtitle", {
        strings: ['愿你一生努力，一生被爱', '面朝大海，春暖花开', '想要的都拥有，得不到的都释怀'],
        startDelay: 0,
        typeSpeed: 300,
        loop: true,
        backSpeed: 100,
        showCursor: true
      });
    } catch (err) {
      console.log(err)
    }
  </script>
  
<div id="main">
  <section class="outer">
  
  <ul class="ads">
    
        <li>
            <a target="_blank" rel="noopener" href="https://curl.qcloud.com/kvO7hb43">
                <img src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/ten_1.jpg" width="300" alt="云服务器限时秒杀">
            </a>
        </li>
    
        <li>
            <a target="_blank" rel="noopener" href="https://www.vultr.com/?ref=8630075">
                <img src="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/img/vultr.png" width="300" alt="vultr优惠vps">
            </a>
        </li>
    
</ul>
  
  
  

<div class="notice" style="margin-top:50px">
    <i class="ri-heart-fill"></i>
    <div class="notice-content" id="broad"></div>
</div>
<script type="text/javascript">
    fetch('https://v1.hitokoto.cn')
        .then(response => response.json())
        .then(data => {
            document.getElementById("broad").innerHTML = data.hitokoto;
        })
        .catch(console.error)
</script>

<style>
    .notice {
        padding: 20px;
        border: 1px dashed #e6e6e6;
        color: #969696;
        position: relative;
        display: inline-block;
        width: 100%;
        background: #fbfbfb50;
        border-radius: 10px;
    }

    .notice i {
        float: left;
        color: #999;
        font-size: 16px;
        padding-right: 10px;
        vertical-align: middle;
        margin-top: -2px;
    }

    .notice-content {
        display: initial;
        vertical-align: middle;
    }
</style>
  
  <article class="articles">
    
    
    
    
    <article
  id="post-mysql"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2021/11/29/mysql/"
    >MySQL知识点&amp;面试题总结</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2021/11/29/mysql/" class="article-date">
  <time datetime="2021-11-29T08:08:56.702Z" itemprop="datePublished">2021-11-29</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/MySQL/">MySQL</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="MySQL-基础"><a href="#MySQL-基础" class="headerlink" title="MySQL 基础"></a>MySQL 基础</h2><h3 id="关系型数据库介绍"><a href="#关系型数据库介绍" class="headerlink" title="关系型数据库介绍"></a>关系型数据库介绍</h3><p>顾名思义，关系型数据库就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系（一对一、一对多、多对多）。</p>
<p>关系型数据库中，我们的数据都被存放在了各种表中（比如用户表），表中的每一行就存放着一条数据（比如一个用户的信息）。</p>
<p><img src="https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/5e3c1a71724a38245aa43b02_99bf70d46cc247be878de9d3a88f0c44.png"></p>
<p>大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且，大部分关系型数据库都支持事务的四大特性(ACID)。</p>
<p><strong>有哪些常见的关系型数据库呢？</strong></p>
<p>MySQL、PostgreSQL、Oracle、SQL Server、SQLite（微信本地的聊天记录的存储就是用的 SQLite） ……。</p>
<h3 id="MySQL-介绍"><a href="#MySQL-介绍" class="headerlink" title="MySQL 介绍"></a>MySQL 介绍</h3><p><img src="https://img-blog.csdnimg.cn/20210327143351823.png"></p>
<p><strong>MySQL 是一种关系型数据库，主要用于持久化存储我们的系统中的一些数据比如用户信息。</strong></p>
<p>由于 MySQL 是开源免费并且比较成熟的数据库，因此，MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是<strong>3306</strong>。</p>
<h2 id="存储引擎"><a href="#存储引擎" class="headerlink" title="存储引擎"></a>存储引擎</h2><h3 id="存储引擎相关的命令"><a href="#存储引擎相关的命令" class="headerlink" title="存储引擎相关的命令"></a>存储引擎相关的命令</h3><p><strong>查看 MySQL 提供的所有存储引擎</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> engines;</span><br></pre></td></tr></table></figure>

<p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/mysql-engines.png" alt="查看MySQL提供的所有存储引擎"></p>
<p>从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB，并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎，也就是说只有 InnoDB 支持事务。</p>
<p><strong>查看 MySQL 当前默认的存储引擎</strong></p>
<p>我们也可以通过下面的命令查看默认的存储引擎。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">show</span> variables <span class="keyword">like</span> <span class="string">&#x27;%storage_engine%&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p><strong>查看表的存储引擎</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> <span class="keyword">table</span> status <span class="keyword">like</span> &quot;table_name&quot; ;</span><br></pre></td></tr></table></figure>

<p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E6%9F%A5%E7%9C%8B%E8%A1%A8%E7%9A%84%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E.png" alt="查看表的存储引擎"></p>
<h3 id="MyISAM-和-InnoDB-的区别"><a href="#MyISAM-和-InnoDB-的区别" class="headerlink" title="MyISAM 和 InnoDB 的区别"></a>MyISAM 和 InnoDB 的区别</h3><p><img src="https://img-blog.csdnimg.cn/20210327145248960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0MzM3Mjcy,size_16,color_FFFFFF,t_70"></p>
<p>MySQL 5.5 之前，MyISAM 引擎是 MySQL 的默认存储引擎，可谓是风光一时。</p>
<p>虽然，MyISAM 的性能还行，各种特性也还不错（比如全文索引、压缩、空间函数等）。但是，MyISAM 不支持事务和行级锁，而且最大的缺陷就是崩溃后无法安全恢复。</p>
<p>5.5 版本之后，MySQL 引入了 InnoDB（事务性数据库引擎），MySQL 5.5 版本后默认的存储引擎为 InnoDB。小伙子，一定要记好这个 InnoDB ，你每次使用 MySQL 数据库都是用的这个存储引擎吧？</p>
<p>言归正传！咱们下面还是来简单对比一下两者：</p>
<p><strong>1.是否支持行级锁</strong></p>
<p>MyISAM 只有表级锁(table-level locking)，而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。</p>
<p>也就说，MyISAM 一锁就是锁住了整张表，这在并发写的情况下是多么滴憨憨啊！这也是为什么 InnoDB 在并发写的时候，性能更牛皮了！</p>
<p><strong>2.是否支持事务</strong></p>
<p>MyISAM 不提供事务支持。</p>
<p>InnoDB 提供事务支持，具有提交(commit)和回滚(rollback)事务的能力。</p>
<p><strong>3.是否支持外键</strong></p>
<p>MyISAM 不支持，而 InnoDB 支持。</p>
<p>🌈 拓展一下：</p>
<p>一般我们也是不建议在数据库层面使用外键的，应用层面可以解决。不过，这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。</p>
<p><strong>4.是否支持数据库异常崩溃后的安全恢复</strong></p>
<p>MyISAM 不支持，而 InnoDB 支持。</p>
<p>使用 InnoDB 的数据库在异常崩溃后，数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 <code>redo log</code> 。</p>
<p>🌈 拓展一下：</p>
<ul>
<li>MySQL InnoDB 引擎使用 <strong>redo log(重做日志)</strong> 保证事务的<strong>持久性</strong>，使用 <strong>undo log(回滚日志)</strong> 来保证事务的<strong>原子性</strong>。</li>
<li>MySQL InnoDB 引擎通过 <strong>锁机制</strong>、<strong>MVCC</strong> 等手段来保证事务的隔离性（ 默认支持的隔离级别是 <strong><code>REPEATABLE-READ</code></strong> ）。</li>
<li>保证了事务的持久性、原子性、隔离性之后，一致性才能得到保障。</li>
</ul>
<p><strong>5.是否支持 MVCC</strong></p>
<p>MyISAM 不支持，而 InnoDB 支持。</p>
<p>讲真，这个对比有点废话，毕竟 MyISAM 连行级锁都不支持。</p>
<p>MVCC 可以看作是行级锁的一个升级，可以有效减少加锁操作，提供性能。</p>
<h3 id="关于-MyISAM-和-InnoDB-的选择问题"><a href="#关于-MyISAM-和-InnoDB-的选择问题" class="headerlink" title="关于 MyISAM 和 InnoDB 的选择问题"></a>关于 MyISAM 和 InnoDB 的选择问题</h3><p>大多数时候我们使用的都是 InnoDB 存储引擎，在某些读密集的情况下，使用 MyISAM 也是合适的。不过，前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点（可是~我们一般都会介意啊！）。</p>
<p>《MySQL 高性能》上面有一句话这样写到:</p>
<blockquote>
<p>不要轻易相信“MyISAM 比 InnoDB 快”之类的经验之谈，这个结论往往不是绝对的。在很多我们已知场景中，InnoDB 的速度都可以让 MyISAM 望尘莫及，尤其是用到了聚簇索引，或者需要访问的数据都可以放入内存的应用。</p>
</blockquote>
<p>一般情况下我们选择 InnoDB 都是没有问题的，但是某些情况下你并不在乎可扩展能力和并发能力，也不需要事务支持，也不在乎崩溃后的安全恢复问题的话，选择 MyISAM 也是一个不错的选择。但是一般情况下，我们都是需要考虑到这些问题的。</p>
<p>因此，对于咱们日常开发的业务系统来说，你几乎找不到什么理由再使用 MyISAM 作为自己的 MySQL 数据库的存储引擎。</p>
<h2 id="锁机制与-InnoDB-锁算法"><a href="#锁机制与-InnoDB-锁算法" class="headerlink" title="锁机制与 InnoDB 锁算法"></a>锁机制与 InnoDB 锁算法</h2><p><strong>MyISAM 和 InnoDB 存储引擎使用的锁：</strong></p>
<ul>
<li>MyISAM 采用表级锁(table-level locking)。</li>
<li>InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁</li>
</ul>
<p><strong>表级锁和行级锁对比：</strong></p>
<ul>
<li><strong>表级锁：</strong> MySQL 中锁定 <strong>粒度最大</strong> 的一种锁，对当前操作的整张表加锁，实现简单，资源消耗也比较少，加锁快，不会出现死锁。其锁定粒度最大，触发锁冲突的概率最高，并发度最低，MyISAM 和 InnoDB 引擎都支持表级锁。</li>
<li><strong>行级锁：</strong> MySQL 中锁定 <strong>粒度最小</strong> 的一种锁，只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小，并发度高，但加锁的开销也最大，加锁慢，会出现死锁。</li>
</ul>
<p><strong>InnoDB 存储引擎的锁的算法有三种：</strong></p>
<ul>
<li>Record lock：记录锁，单个行记录上的锁</li>
<li>Gap lock：间隙锁，锁定一个范围，不包括记录本身</li>
<li>Next-key lock：record+gap 临键锁，锁定一个范围，包含记录本身</li>
</ul>
<h2 id="查询缓存"><a href="#查询缓存" class="headerlink" title="查询缓存"></a>查询缓存</h2><p>执行查询语句的时候，会先查询缓存。不过，MySQL 8.0 版本后移除，因为这个功能不太实用</p>
<p><code>my.cnf</code> 加入以下配置，重启 MySQL 开启查询缓存</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">query_cache_type</span>=<span class="string">1</span></span><br><span class="line"><span class="attr">query_cache_size</span>=<span class="string">600000</span></span><br></pre></td></tr></table></figure>

<p>MySQL 执行以下命令也可以开启查询缓存</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">set</span> <span class="string">global  query_cache_type=1;</span></span><br><span class="line"><span class="attr">set</span> <span class="string">global  query_cache_size=600000;</span></span><br></pre></td></tr></table></figure>

<p>如上，<strong>开启查询缓存后在同样的查询条件以及数据情况下，会直接在缓存中返回结果</strong>。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。（<strong>查询缓存不命中的情况：（1）</strong>）因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外，（<strong>查询缓存不命中的情况：（2）</strong>）如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表，其查询结果也不会被缓存。</p>
<p>（<strong>查询缓存不命中的情况：（3）</strong>）<strong>缓存建立之后</strong>，MySQL 的查询缓存系统会跟踪查询中涉及的每张表，如果这些表（数据或结构）发生变化，那么和这张表相关的所有缓存数据都将失效。</p>
<p><strong>缓存虽然能够提升数据库的查询性能，但是缓存同时也带来了额外的开销，每次查询后都要做一次缓存操作，失效后还要销毁。</strong> 因此，开启查询缓存要谨慎，尤其对于写密集的应用来说更是如此。如果开启，要注意合理控制缓存空间大小，一般来说其大小设置为几十 MB 比较合适。此外，<strong>还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存：</strong></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> sql_no_cache <span class="built_in">count</span>(<span class="operator">*</span>) <span class="keyword">from</span> usr;</span><br></pre></td></tr></table></figure>

<h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><h3 id="何为事务？"><a href="#何为事务？" class="headerlink" title="何为事务？"></a>何为事务？</h3><p>一言蔽之，<strong>事务是逻辑上的一组操作，要么都执行，要么都不执行。</strong></p>
<p><strong>可以简单举一个例子不？</strong></p>
<p>事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元，这个转账会涉及到两个关键操作就是：</p>
<ol>
<li>将小明的余额减少 1000 元</li>
<li>将小红的余额增加 1000 元。</li>
</ol>
<p>事务会把这两个操作就可以看成逻辑上的一个整体，这个整体包含的操作要么都成功，要么都要失败。</p>
<p>这样就不会出现小明余额减少而小红的余额却并没有增加的情况。</p>
<h3 id="何为数据库事务？"><a href="#何为数据库事务？" class="headerlink" title="何为数据库事务？"></a>何为数据库事务？</h3><p>数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话，你接触到的往往就是数据库事务了。</p>
<p>平时，我们在谈论事务的时候，如果没有特指<strong>分布式事务</strong>，往往指的就是<strong>数据库事务</strong>。</p>
<p><strong>那数据库事务有什么作用呢？</strong></p>
<p>简单来说：数据库事务可以保证多个对数据库的操作（也就是 SQL 语句）构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循：<strong>要么全部执行成功,要么全部不执行</strong> 。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 开启一个事务</span><br><span class="line"><span class="keyword">START</span> TRANSACTION;</span><br><span class="line"># 多条 <span class="keyword">SQL</span> 语句</span><br><span class="line">SQL1,SQL2...</span><br><span class="line">## 提交事务</span><br><span class="line"><span class="keyword">COMMIT</span>;</span><br></pre></td></tr></table></figure>

<p><img src="https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/2020-12/640-20201207160554677.png"></p>
<p>另外，关系型数据库（例如：<code>MySQL</code>、<code>SQL Server</code>、<code>Oracle</code> 等）事务都有 <strong>ACID</strong> 特性：</p>
<p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E4%BA%8B%E5%8A%A1%E7%89%B9%E6%80%A7.png" alt="事务的特性"></p>
<h3 id="何为-ACID-特性呢？"><a href="#何为-ACID-特性呢？" class="headerlink" title="何为 ACID 特性呢？"></a>何为 ACID 特性呢？</h3><ol>
<li><strong>原子性</strong>（<code>Atomicity</code>） ： 事务是最小的执行单位，不允许分割。事务的原子性确保动作要么全部完成，要么完全不起作用；</li>
<li><strong>一致性</strong>（<code>Consistency</code>）： 执行事务前后，数据保持一致，例如转账业务中，无论事务是否成功，转账者和收款人的总额应该是不变的；</li>
<li><strong>隔离性</strong>（<code>Isolation</code>）： 并发访问数据库时，一个用户的事务不被其他事务所干扰，各并发事务之间数据库是独立的；</li>
<li><strong>持久性</strong>（<code>Durability</code>）： 一个事务被提交之后。它对数据库中数据的改变是持久的，即使数据库发生故障也不应该对其有任何影响。</li>
</ol>
<p><strong>数据事务的实现原理呢？</strong></p>
<p>我们这里以 MySQL 的 InnoDB 引擎为例来简单说一下。</p>
<p>MySQL InnoDB 引擎使用 <strong>redo log(重做日志)</strong> 保证事务的<strong>持久性</strong>，使用 <strong>undo log(回滚日志)</strong> 来保证事务的<strong>原子性</strong>。</p>
<p>MySQL InnoDB 引擎通过 <strong>锁机制</strong>、<strong>MVCC</strong> 等手段来保证事务的隔离性（ 默认支持的隔离级别是 <strong><code>REPEATABLE-READ</code></strong> ）。</p>
<p>保证了事务的持久性、原子性、隔离性之后，一致性才能得到保障。</p>
<h3 id="并发事务带来哪些问题"><a href="#并发事务带来哪些问题" class="headerlink" title="并发事务带来哪些问题?"></a>并发事务带来哪些问题?</h3><p>在典型的应用程序中，多个事务并发运行，经常会操作相同的数据来完成各自的任务（多个用户对同一数据进行操作）。并发虽然是必须的，但可能会导致以下的问题。</p>
<ul>
<li><strong>脏读（Dirty read）:</strong> 当一个事务正在访问数据并且对数据进行了修改，而这种修改还没有提交到数据库中，这时另外一个事务也访问了这个数据，然后使用了这个数据。因为这个数据是还没有提交的数据，那么另外一个事务读到的这个数据是“脏数据”，依据“脏数据”所做的操作可能是不正确的。</li>
<li><strong>丢失修改（Lost to modify）:</strong> 指在一个事务读取一个数据时，另外一个事务也访问了该数据，那么在第一个事务中修改了这个数据后，第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失，因此称为丢失修改。 例如：事务 1 读取某表中的数据 A=20，事务 2 也读取 A=20，事务 1 修改 A=A-1，事务 2 也修改 A=A-1，最终结果 A=19，事务 1 的修改被丢失。</li>
<li><strong>不可重复读（Unrepeatable read）:</strong> 指在一个事务内多次读同一数据。在这个事务还没有结束时，另一个事务也访问该数据。那么，在第一个事务中的两次读数据之间，由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况，因此称为不可重复读。</li>
<li><strong>幻读（Phantom read）:</strong> 幻读与不可重复读类似。它发生在一个事务（T1）读取了几行数据，接着另一个并发事务（T2）插入了一些数据时。在随后的查询中，第一个事务（T1）就会发现多了一些原本不存在的记录，就好像发生了幻觉一样，所以称为幻读。</li>
</ul>
<p><strong>不可重复读和幻读区别：</strong></p>
<p>不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改，幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。</p>
<h3 id="事务隔离级别有哪些"><a href="#事务隔离级别有哪些" class="headerlink" title="事务隔离级别有哪些?"></a>事务隔离级别有哪些?</h3><p>SQL 标准定义了四个隔离级别：</p>
<ul>
<li><strong>READ-UNCOMMITTED(读取未提交)：</strong> 最低的隔离级别，允许读取尚未提交的数据变更，<strong>可能会导致脏读、幻读或不可重复读</strong>。</li>
<li><strong>READ-COMMITTED(读取已提交)：</strong> 允许读取并发事务已经提交的数据，<strong>可以阻止脏读，但是幻读或不可重复读仍有可能发生</strong>。</li>
<li><strong>REPEATABLE-READ(可重复读)：</strong> 对同一字段的多次读取结果都是一致的，除非数据是被本身事务自己所修改，<strong>可以阻止脏读和不可重复读，但幻读仍有可能发生</strong>。</li>
<li><strong>SERIALIZABLE(可串行化)：</strong> 最高的隔离级别，完全服从 ACID 的隔离级别。所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，<strong>该级别可以防止脏读、不可重复读以及幻读</strong>。</li>
</ul>
<hr>
<table>
<thead>
<tr>
<th align="center">隔离级别</th>
<th align="center">脏读</th>
<th align="center">不可重复读</th>
<th align="center">幻读</th>
</tr>
</thead>
<tbody><tr>
<td align="center">READ-UNCOMMITTED</td>
<td align="center">√</td>
<td align="center">√</td>
<td align="center">√</td>
</tr>
<tr>
<td align="center">READ-COMMITTED</td>
<td align="center">×</td>
<td align="center">√</td>
<td align="center">√</td>
</tr>
<tr>
<td align="center">REPEATABLE-READ</td>
<td align="center">×</td>
<td align="center">×</td>
<td align="center">√</td>
</tr>
<tr>
<td align="center">SERIALIZABLE</td>
<td align="center">×</td>
<td align="center">×</td>
<td align="center">×</td>
</tr>
</tbody></table>
<h3 id="MySQL-的默认隔离级别是什么"><a href="#MySQL-的默认隔离级别是什么" class="headerlink" title="MySQL 的默认隔离级别是什么?"></a>MySQL 的默认隔离级别是什么?</h3><p>MySQL InnoDB 存储引擎的默认支持的隔离级别是 <strong>REPEATABLE-READ（可重读）</strong>。我们可以通过<code>SELECT @@tx_isolation;</code>命令来查看，MySQL 8.0 该命令改为<code>SELECT @@transaction_isolation;</code></p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">SELECT</span> @<span class="variable">@tx</span>_isolation;</span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span> @<span class="variable">@tx</span>_isolation  <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br><span class="line"><span class="operator">|</span> REPEATABLE<span class="operator">-</span>READ <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-----------------+</span></span><br></pre></td></tr></table></figure>

<p><del>这里需要注意的是：与 SQL 标准不同的地方在于 InnoDB 存储引擎在 <strong>REPEATABLE-READ（可重读）</strong> 事务隔离级别下使用的是 Next-Key Lock 锁算法，因此可以避免幻读的产生，这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 <strong>REPEATABLE-READ（可重读）</strong> 已经可以完全保证事务的隔离性要求，即达到了 SQL 标准的 <strong>SERIALIZABLE(可串行化)</strong> 隔离级别。</del></p>
<p>🐛 问题更正：<strong>MySQL InnoDB 的 REPEATABLE-READ（可重读）并不保证避免幻读，需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。</strong></p>
<p>因为隔离级别越低，事务请求的锁越少，所以大部分数据库系统的隔离级别都是 <strong>READ-COMMITTED(读取提交内容)</strong> ，但是你要知道的是 InnoDB 存储引擎默认使用 <strong>REPEATABLE-READ（可重读）</strong> 并不会有任何性能损失。</p>
<p>InnoDB 存储引擎在 <strong>分布式事务</strong> 的情况下一般会用到 <strong>SERIALIZABLE(可串行化)</strong> 隔离级别。</p>
<p>🌈 拓展一下(以下内容摘自《MySQL 技术内幕：InnoDB 存储引擎(第 2 版)》7.7 章)：</p>
<blockquote>
<p>InnoDB 存储引擎提供了对 XA 事务的支持，并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源（transactional resources）参与到一个全局的事务中。事务资源通常是关系型数据库系统，但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交，要么都回滚，这对于事务原有的 ACID 要求又有了提高。另外，在使用分布式事务时，InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。</p>
</blockquote>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li>《高性能 MySQL》</li>
<li><a target="_blank" rel="noopener" href="https://www.omnisci.com/technical-glossary/relational-database">https://www.omnisci.com/technical-glossary/relational-database</a></li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/MySQL/" rel="tag">MySQL</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-Spring全家桶/SpringSecurity"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2021/11/26/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/SpringSecurity/"
    >SpringSecurity</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2021/11/26/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/SpringSecurity/" class="article-date">
  <time datetime="2021-11-26T09:00:00.000Z" itemprop="datePublished">2021-11-26</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/SpringBoot/">SpringBoot</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/dl610455894/p/14072960.html">spring security简介与使用</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_22172133/article/details/86503223">spring security——基本介绍（一）</a></p>
<hr>
<h3 id="spring-security"><a href="#spring-security" class="headerlink" title="spring security"></a>spring security</h3><p><strong>1. 使用目的</strong>：验证，授权，攻击防护。<br><br><strong>2. 原理</strong>：创建大量的filter和interceptor来进行请求的验证和拦截，以此来达到安全的效果。</p>
<hr>
<h4 id="这里只记录-springboot-整合-spring-security"><a href="#这里只记录-springboot-整合-spring-security" class="headerlink" title="这里只记录 springboot 整合 spring security"></a>这里只记录 springboot 整合 spring security</h4><p>1. 创建springboot项目，切记是新建Spring Initializr<br>1. 添加依赖</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt; </span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>
<p>3. 仅需添加依赖即可启动，启动后通过游览器进入8080端口，会自动跳转到Security内置的login页面</p>
<ul>
<li>用户名默认为user </li>
<li>密码：控制台中有自动生成</li>
</ul>
<p>4. 自定义登录页面</p>
<ul>
<li>我们通过Thymeleaf来实现，先创建一个登录页面<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt; </span><br><span class="line">&lt;html lang=&quot;en&quot; xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt; </span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset=&quot;UTF-8&quot;&gt; </span><br><span class="line">    &lt;title&gt;Title&lt;/title&gt; </span><br><span class="line">&lt;/head&gt; </span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;h1&gt;登录管理&lt;/h1&gt; </span><br><span class="line">    &lt;form th:action=&quot;@&#123;/login&#125;&quot; method=&quot;post&quot;&gt; </span><br><span class="line">        账号:&lt;input type=&quot;text&quot; name=&quot;username&quot;&gt;&lt;br&gt; </span><br><span class="line">        密码:&lt;input type=&quot;password&quot; name=&quot;password&quot;&gt;&lt;br&gt; </span><br><span class="line">        &lt;input type=&quot;submit&quot; value=&quot;登录&quot;&gt;</span><br><span class="line">    &lt;/form&gt; </span><br><span class="line">&lt;/body&gt; </span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure></li>
<li>添加对应的控制器<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">@Controller </span><br><span class="line">public class LoginController &#123;     </span><br><span class="line">    @RequestMapping(&quot;/login.html&quot;) </span><br><span class="line">    public String goLoginPage()&#123; </span><br><span class="line">        return &quot;/login&quot;; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>添加SpringSecurity的配置类<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">package com.bobo.config;</span><br><span class="line"></span><br><span class="line">import org.springframework.context.annotation.Configuration;</span><br><span class="line">import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;</span><br><span class="line">import org.springframework.security.config.annotation.web.builders.HttpSecurity;</span><br><span class="line">import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;</span><br><span class="line"></span><br><span class="line">    @Configuration</span><br><span class="line">    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter &#123;</span><br><span class="line">    /**</span><br><span class="line">    * 先通过内存中的账号密码来处理</span><br><span class="line">    * @param auth</span><br><span class="line">    * @throws Exception</span><br><span class="line">    */</span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(AuthenticationManagerBuilder auth) throws Exception</span><br><span class="line">    &#123;</span><br><span class="line">        auth.inMemoryAuthentication()</span><br><span class="line">            .withUser(&quot;zhang&quot;)</span><br><span class="line">            .password(&quot;&#123;noop&#125;123&quot;)</span><br><span class="line">            .roles(&quot;USER&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">    @Override</span><br><span class="line">    protected void configure(HttpSecurity http) throws Exception &#123;</span><br><span class="line">        http.authorizeRequests()</span><br><span class="line">            .mvcMatchers(&quot;/login_page.html&quot;,&quot;/login.html&quot;,&quot;/css/**&quot;,&quot;/js/**&quot;)</span><br><span class="line">            .permitAll()</span><br><span class="line">            .antMatchers(&quot;/**&quot;)</span><br><span class="line">            .hasAnyRole(&quot;USER&quot;)</span><br><span class="line">            .anyRequest()</span><br><span class="line">            .authenticated()</span><br><span class="line">            .and()</span><br><span class="line">            .formLogin()</span><br><span class="line">            .loginPage(&quot;/login.html&quot;)</span><br><span class="line">            .loginProcessingUrl(&quot;/login&quot;)</span><br><span class="line">            .successForwardUrl(&quot;/home.html&quot;)</span><br><span class="line">            .and()</span><br><span class="line">            .csrf()</span><br><span class="line">            .disable();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<h3 id="5-数据库认证"><a href="#5-数据库认证" class="headerlink" title="5. 数据库认证"></a>5. 数据库认证</h3><ul>
<li>关键配置</li>
</ul>
<p> <img src="5799FF8BC7B74E909B77AB4B1EE851E6" alt="image"></p>
<ul>
<li>授权</li>
</ul>
<p><img src="8C5D92AD6D7746858BFC306922494ED7" alt="image"></p>
<h3 id="6-SpringBoot中的源码分析"><a href="#6-SpringBoot中的源码分析" class="headerlink" title="6. SpringBoot中的源码分析"></a>6. SpringBoot中的源码分析</h3><ul>
<li>前面我们介绍了Spring中整合SpringSecurity的核心源码流程，那么中SpringBoot应该怎么看呢？</li>
</ul>
<p><img src="8FFF97EEDD264648B43479414E43DE04" alt="image"></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguratio n, // 初始化 </span><br><span class="line">org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoCo nfiguration,// 认证 创建默认的账号密码 </span><br><span class="line">org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfig uration // 和过滤器链有关</span><br></pre></td></tr></table></figure>
<ul>
<li>默认账号密码</li>
</ul>
<p><img src="5F125D58A19546D887081857B0D2EA17" alt="image"></p>
<ul>
<li>DelegatingFilterProxy</li>
</ul>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">//</span><br><span class="line">// Source code recreated from a .class file by IntelliJ IDEA</span><br><span class="line">// (powered by FernFlower decompiler)</span><br><span class="line">//</span><br><span class="line">package org.springframework.boot.autoconfigure.security.servlet;</span><br><span class="line">import java.util.EnumSet;</span><br><span class="line">import java.util.stream.Collectors;</span><br><span class="line">import javax.servlet.DispatcherType;</span><br><span class="line">import org.springframework.boot.autoconfigure.AutoConfigureAfter;</span><br><span class="line">import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;</span><br><span class="line">import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;</span><br><span class="line">import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;</span><br><span class="line">import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;</span><br><span class="line">import org.springframework.boot.autoconfigure.security.SecurityProperties;</span><br><span class="line">import org.springframework.boot.context.properties.EnableConfigurationProperties;</span><br><span class="line">import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;</span><br><span class="line">import org.springframework.boot.web.servlet.ServletRegistrationBean;</span><br><span class="line">import org.springframework.context.annotation.Bean;</span><br><span class="line">import org.springframework.context.annotation.Configuration;</span><br><span class="line">import org.springframework.security.config.http.SessionCreationPolicy;</span><br><span class="line">import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;</span><br><span class="line">@Configuration(</span><br><span class="line">    proxyBeanMethods = false</span><br><span class="line">)</span><br><span class="line">@ConditionalOnWebApplication(</span><br><span class="line">    type = Type.SERVLET</span><br><span class="line">)</span><br><span class="line">@EnableConfigurationProperties(&#123;SecurityProperties.class&#125;)</span><br><span class="line">@ConditionalOnClass(&#123;AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class&#125;)</span><br><span class="line">@AutoConfigureAfter(&#123;SecurityAutoConfiguration.class&#125;)</span><br><span class="line">public class SecurityFilterAutoConfiguration &#123;</span><br><span class="line">    private static final String DEFAULT_FILTER_NAME =&quot;springSecurityFilterChain&quot;;</span><br><span class="line">    public SecurityFilterAutoConfiguration() &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    @Bean</span><br><span class="line">    @ConditionalOnBean(</span><br><span class="line">        name = &#123;&quot;springSecurityFilterChain&quot;&#125;</span><br><span class="line">    )</span><br><span class="line">    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) &#123;</span><br><span class="line">        // 获取DelegatingFilterProxy对象，并且设置 url-parttern=/*</span><br><span class="line">        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(&quot;springSecurityFilterChain&quot;, newServletRegistrationBean[0]);</span><br><span class="line">        registration.setOrder(securityProperties.getFilter().getOrder());</span><br><span class="line">        registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));</span><br><span class="line">        return registration;</span><br><span class="line">    &#125;</span><br><span class="line">    private EnumSet&lt;DispatcherType&gt; getDispatcherTypes(SecurityProperties securityProperties) &#123;</span><br><span class="line">    return securityProperties.getFilter().getDispatcherTypes() == null ? null : (EnumSet)securityProperties.getFilter().getDispatcherTypes().stream().map((type)</span><br><span class="line">        -&gt; &#123;</span><br><span class="line">            return DispatcherType.valueOf(type.name());</span><br><span class="line">        &#125;).collect(Collectors.toCollection(() -&gt; &#123;</span><br><span class="line">                return EnumSet.noneOf(DispatcherType.class);</span><br><span class="line">           &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><img src="DA4CE205FF6E46E298DA7D1D867CEA12" alt="image"></p>
<p><img src="917EA5288A7C4F9BBAEA7DDBA8FBB1DA" alt="image"></p>
<ul>
<li>那么后面的处理又是一样的了，进入FilterChainProxy中</li>
</ul>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/SpringSecurity/" rel="tag">SpringSecurity</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/DecimalFormat"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/DecimalFormat/"
    >DecimalFormat</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/DecimalFormat/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><strong>DecimalFormat</strong></p>
<p>作用：</p>
<p>1. 整数和小数都设置最小位数，</p>
<p>2. 设置小数固定位数(同时设置最大最小)，不设置整数位数</p>
<p>注意：1. <strong>非静态类</strong>，需要new</p>
<ol start="2">
<li>没有get和set方法，调用构造方法时需传入参数</li>
</ol>
<p><strong>3. 可以用NumberFormat 取代 DecimalFormat使用</strong></p>
<p>（1）”000.00”——整数位固定为4位，小数位固定位2位。</p>
<p>四舍五入</p>
<p>若实际的数字超出该位数，则按实际输出。</p>
<p>DecimalFormat nf = new DecimalFormat(&quot;0000.000&quot;);</p>
<p>（2）&quot; # &quot; ： 去掉所有小数位，舍弃的小数部分四舍五入</p>
<p>&quot; .## &quot; ：#的个数=小数的位数，舍弃整数部分，舍弃的小数四舍五入，</p>
<p>private static void decimalFormat() { double d1 = 123456.36987, d2 = 12.3698; DecimalFormat nf = new DecimalFormat(&quot;0000.000&quot;); //d1=123456.370 d2=0012.370 System.out.println(&quot;d1=&quot; + nf.format(d1) + &quot; d2=&quot; + nf.format(d2)); nf = new DecimalFormat(&quot;#&quot;); System.out.println(&quot;d1=&quot; + nf.format(d1)); //d1=123456 nf = new DecimalFormat(&quot;.####&quot;); System.out.println(&quot;d1=&quot; + nf.format(d1));//d1=123456.3699 //注意”,”不能放在小数部分 nf = new DecimalFormat(&quot;0000,0000.00000&quot;); System.out.println(&quot;d1=&quot; + nf.format(d1));//d1=0012,3456.36987 }</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/DecimalFormat/" rel="tag">DecimalFormat</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/BigDecimal"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/BigDecimal/"
    >BigDecimal</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/BigDecimal/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><strong>1.简介</strong></p>
<p>（1）BigDecimal类位于java.math.BigDecimal包下。</p>
<p>（2）可以完成大的小数操作，而且也可以使用此类进行精确的四舍五入，这一点在开发中经常使用。</p>
<p>（3）对于不需要任何准确计算精度的程序可以直接使用float或double完成，但是如果需要精确计算结果，则必须使用BigDecimal类。</p>
<p>（4）举个例子，怎么输出1/3的准确值？直接输出1/3是0，只能用BigDecimal</p>
<p><strong>2.BigDecimal类常用方法</strong></p>
<p>BigDecimal(String val) 构造方法，将String类型转换成BigDecimal类型数据。 BigDecimal(double val) 构造方法，将double类型转换成BigDecimal类型数据。 BigDecimal(int val) 构造方法，将int类型转换成BigDecimal类型数据。 BigDecimal add(BigDecimal value) 加法，求两个BigDecimal类型数据的和。 BigDecimal subtract(BigDecimal value) 减法，求两个BigDecimal类型数据的差。 BigDecimal multiply(BigDecimal value) 乘法，求两个BigDecimal类型数据的积。 BigDecimal divide(BigDecimal divisor) 除法，求两个BigDecimal类型数据的商。 BigDecimal remainder(BigDecimal divisor) 求余数,求BigDecimal除以divisor的余数 BigDecimal max(BigDecimal value) 最大数，求两个BigDecimal类型数据的最大值。 BigDecimal min(BigDecimal value) 最小数，求两个BigDecimal类型数据的最小值。 BigDecimal abs() 绝对值，求BigDecimal类型数据的绝对值。 BigDecimal negate() 相反数，求BigDecimal类型数据的相反数。</p>
<p>这里有一点需要注意的是除法运算divide。</p>
<p>BigDecimal除法可能出现不能整除的情况，比如 4.5/1.3，这时会报错</p>
<p><strong>其实divide有三个参数的方法</strong></p>
<p>BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)</p>
<p>第一个参数表示除数。</p>
<p>第二个参数表示小数点后保留位数。</p>
<p>第三个参数表示舍入模式。只有在作除法运算或四舍五入时才用到舍入模式。</p>
<p>舍入模式有下面这几种： ROUND_CEILING //向正无穷方向舍入 ROUND_DOWN //向零方向舍入 ROUND_FLOOR //向负无穷方向舍入 ROUND_HALF_DOWN //向（距离）最近的一边舍入，除非两边（的距离）是相等,如果是这样，向下舍入, 例如1.55 保留一位小数结果为1.5 ROUND_HALF_EVEN //向（距离）最近的一边舍入，除非两边（的距离）是相等,如果是这样，如果保留位 数是奇数，使用ROUND_HALF_UP，如果是偶数，使用ROUND_HALF_DOWN ROUND_HALF_UP //向（距离）最近的一边舍入，除非两边（的距离）是相等,如果是这样，向上舍入, 1.55 保留一位小数结果为1.6 ROUND_UNNECESSARY //计算结果是精确的，不需要舍入模式 ROUND_UP //向远离0的方向舍入</p>
<p><strong>3. 步骤1：对字符串使用构造函数</strong></p>
<p><strong>步骤2：对BigDecimal数据进行操作</strong></p>
<p><strong>步骤3：double c3 = b5.doubleValue() //由BigDecimal类型转为double类型</strong></p>
<p><strong>4. 总结：</strong></p>
<p>(1) 商业计算使用BigDecimal。</p>
<p>(2) 尽量使用参数类型为String的构造函数。</p>
<p>(3) BigDecimal都是不可变的（immutable）的，在进行每一步运算时，都会产生一个新的对象，所以在做加减乘除运算时千万要保存操作后的值。</p>
<p><strong>5. BigDecimal 小数点处理</strong></p>
<p>需求：计算结果保留三位小数。</p>
<p>/** * 方式1 * 0.000：三位小数 同理 0.00：两位小数 依次类推... * */ double a=3.154215; DecimalFormat myformat=new java.text.DecimalFormat(&quot;0.000&quot;); String result1 = myformat.format(a); Log.d(&quot;TAG&quot;,a+&quot;保留三位小数：&quot;+result1); /** * 方式2 * #.000：三位小数 同理 #.00：两位小数 * */ double b=3.256321; DecimalFormat df=new DecimalFormat(&quot;#.000&quot;); String result2=df.format(b); Log.d(&quot;TAG&quot;,b+&quot;保留三位小数：&quot;+result2); /** * BigDecimal类操作保留几位小数 * */ double c=3.558525; BigDecimal bigDecimal=new BigDecimal(c); double result3=bigDecimal.setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue(); Log.d(&quot;TAG&quot;,c+&quot;保留三位小数并四舍五入：&quot;+result3); 结果： 3.154215保留三位小数：3.154 3.256321保留三位小数：3.256 3.558525保留三位小数并四舍五入：3.559</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/BigDecimal/" rel="tag">BigDecimal</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/Iterator迭代器"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Iterator%E8%BF%AD%E4%BB%A3%E5%99%A8/"
    >Iterator</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Iterator%E8%BF%AD%E4%BB%A3%E5%99%A8/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><strong>Iterator（迭代器）</strong></p>
<p>Java Iterator（迭代器）不是一个集合，它是一种用于<strong>访问集合</strong>的方法，可用于迭代 ArrayList 和 HashSet 等集合。</p>
<p>Iterator 是 Java 迭代器最简单的实现，ListIterator 是 Collection API 中的接口， 它扩展了 Iterator 接口。</p>
<p>迭代器 it 的两个基本操作是 next 、hasNext 和 remove。</p>
<p>调用 it.next() 会返回迭代器的下一个元素，并且更新迭代器的状态。</p>
<p>调用 it.hasNext() 用于检测集合中是否还有元素。</p>
<p>调用 it.remove() 将迭代器返回的元素删除。</p>
<p><strong>ArrayList[0] 是错误语法</strong></p>
<p><strong>迭代器有数据类型的，迭代器初始位置在-1</strong></p>
<p><strong>import</strong> java.util.ArrayList; <strong>import</strong> java.util.Iterator; <strong>public</strong> <strong>class</strong> RunoobTest {     <strong>public</strong> <strong>static</strong> <strong>void</strong> main(String[] args) {         <em>// 创建集合</em>         ArrayList&lt;String&gt; sites = <strong>new</strong> ArrayList&lt;String&gt;();         sites.add(&quot;Google&quot;);         sites.add(&quot;Runoob&quot;);         sites.add(&quot;Taobao&quot;);         sites.add(&quot;Zhihu&quot;);         <em>// 获取迭代器</em>         Iterator&lt;String&gt; it = sites.iterator();         <em>// 输出集合中的所有元素</em>         <strong>while</strong>(it.hasNext()) {             System.out.println(it.next());         }     } } 执行以上代码，输出结果如下： Google Runoob Taobao Zhihu</p>
<p>删除元素：要删除集合中的元素可以使用 remove() 方法。</p>
<p>删除集合中小于 10 的元素： // 引入 ArrayList 和 Iterator 类 import java.util.ArrayList; import java.util.Iterator; public class RunoobTest {     public static void main(String[] args) {         ArrayList&lt;Integer&gt; numbers = new ArrayList&lt;Integer&gt;();         numbers.add(12);         numbers.add(8);         numbers.add(2);         numbers.add(23);         Iterator&lt;Integer&gt; it = numbers.iterator();         while(it.hasNext()) {             Integer i = it.next();             if(i &lt; 10) {                   it.remove();  // 删除小于 10 的元素             }         }         System.out.println(numbers);     } } 执行以上代码，输出结果如下： [12, 23]</p>
<p>如何删除集合元素？</p>
<p>直接用迭代器的remove()删除，或</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/Iterator/" rel="tag">Iterator</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/NumberFormat"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/NumberFormat/"
    >NumberFormat</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/NumberFormat/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>在工作当中，可能经常会遇到比如数据保留两位小数显示，去除后面多余0，按指定格式输出数据这种需求，有时隔得时间久了也难免会忘记，所以就稍作总结方便今后查看，同时最后提供一个工具类方便今后使用。</p>
<p><strong>NumberFormat</strong></p>
<p>NumberFormat 是所有数值格式的抽象基类。此类提供格式化和解析数值的接口。NumberFormat 还提供了一些方法来确定哪些语言环境具有数值格式，以及它们的名称是什么。</p>
<p>NumberFormat 可用于格式化和解析任何语言环境的数值。使代码能够完全独立于小数点、千位分隔符甚至所用特定小数位数的语言环境约定，并与数值格式是否为偶小数无关。</p>
<p><strong>数值格式化</strong></p>
<p><strong>注意： 1. 不能new，只能用get方法获取对象</strong></p>
<p><strong>2. 构造方法不能传入参数，只能用set方法</strong></p>
<p><strong>3. 默认最大小数点三位</strong></p>
<p>1. getInstance()和getNumberInstance()作用完全一样</p>
<p>2. 默认小数点后最多三位即.setMaximumFractionDigits,无其他默认</p>
<p>3. 若数字原本的整数(小数)位数大于设定的最小位数，则保持原样输出</p>
<p>4. set是void，必须放在语句最后，后面不能有其他方法</p>
<p>//错误写法： System.out.println(NumberFormat.getInstance() .setMaximumFractionDigits(2) .format(d));</p>
<p>getInstance()、getNumberInstance() 返回当前默认语言环境的通用数值格式。 getInstance(Locale.)、getNumberInstance(Locale.) 返回指定语言环境的通用数值格式。</p>
<p>NumberFormat.setMinimumIntegerDigits(int) 设置数的整数部分所允许的最小位数。 NumberFormat.setMaximumIntegerDigits(int) 设置数的整数部分所允许的最大位数。 NumberFormat.setMinimumFractionDigits(int) 设置最少小数点位数，不足的位数以0补位，超出的话按实际位数输出。 NumberFormat.setMaximumFractionDigits(int) 设置最多保留小数位数，不足不补0。</p>
<p>public class HelloWorld { private static void main(String[] args) { double d = 12345.676688000; // 第一步：获取NumberFormat对象 NumberFormat nf = NumberFormat.getNumberInstance(); NumberFormat nf2 = NumberFormat.getInstance(); //等同上句 // 第二步：设置NumberFormat对象的规范 nf.setMinimumIntegerDigits(2); 整数部分最大位数 nf.setMaximumIntegerDigits(3); 整数部分最小位数 nf.setMinimumFractionDigits(1); 小数部分最大位数 nf.setMaximumFractionDigits(1); 小数部分最小位数 // 第三步：format(目标数) System.out.println(nf.format(d)); //12,345.677 //1. 默认只保留到小数点后三位 //2. 前两步可以合在一起，但第三步不能和第二步合在一起 // 设置地点： nf = NumberFormat.getNumberInstance(Locale.US); d = 12345.6789; System.out.println(nf.format(d));//12,345.679 nf = NumberFormat.getNumberInstance(Locale.FRANCE); System.out.println(nf.format(d));//12 345,679 } }</p>
<p><strong>货币格式化</strong></p>
<p>注意：</p>
<p>1. 静态，不能new，需要用到getCurrencyInstance()</p>
<p>2. 没有set方法</p>
<p>3. 默认小数取 2 位</p>
<p>3. 默认为中国货币，输出：￥123.46</p>
<p>1. getCurrencyInstance()。静态方法，建立一个NumberFormat类的对象并返回引用，该引用指定货币格式为系统预设的货币格式。</p>
<p>2. getCurrencyInstance(Locale) 。静态方法，建立一个NumberFormat类的对象，并返回引用，该引用的货币格式由Locale指定。Locale类在java.util包中。</p>
<p>public class HelloWorld { private static void main(String[] args) { NumberFormat nf = NumberFormat.getCurrencyInstance(); System.out.println(nf.format(123.456)); // ￥123.46 //按指定的货币格式输出，这里是美元 nf = NumberFormat.getCurrencyInstance(Locale.US); System.out.println(nf.format(123.456)); // $123.46 } }</p>
<p><strong>百分比格式化</strong></p>
<p>1. getPercentInstance()。静态方法，创建一个NumberFormat类的对象并返回其引用。该对象指定百分比格式为系统预设格式。</p>
<p>2. getPercentInstance(Locale)。静态方法，创建一个NumberFormat类的对象并返回引用。该对象的百分比格式由Locale来指定。</p>
<p>private static void percentFormat() { NumberFormat nf = NumberFormat.getPercentInstance(); double d = 123.456; System.out.println(nf.format(d));//12,346% // 法国格式 nf = NumberFormat.getPercentInstance(Locale.FRANCE); System.out.println(nf.format(d));//12 346 % }</p>
<p>附：法国格式就是用空格取代逗号</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/NumberFormat/" rel="tag">NumberFormat</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/Math"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Math/"
    >Math</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Math/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>Math 类封装了常用的数学运算，提供了基本的数学操作，如指数、对数、平方根和三角函数等。Math 类位于 java.lang 包，它的<strong>构造方法是 private</strong> 的，因此<strong>无法创建 Math 类的对象</strong>，并且 Math 类中的所有方法都是类方法，可以直接通过类名来调用它们。</p>
<p>下面详细介绍该类的常量及数学处理方法。</p>
<p><strong>静态常量</strong></p>
<p>Math 类中包含 E 和 PI 两个静态常量，正如它们名字所暗示的，它们的值分别等于 e（自然对数）和 π（圆周率）。</p>
<p>System.out.println(&quot;E 常量的值：&quot; + Math.E); System.out.println(&quot;PI 常量的值：&quot; + Math.PI);</p>
<p>E 常量的值：2.718281828459045 PI 常量的值：3.141592653589793</p>
<p><strong>求最大值、最小值和绝对值</strong></p>
<p>在程序中常见的就是求最大值、最小值和绝对值问题，如果使用 Math 类提供的方法可以很容易实现。这些方法的说明如表 1 所示。</p>
<hr>
<p>  方法                                   说明</p>
<p>  static int abs(int a)                  返回 a 的绝对值</p>
<p>  static long abs(long a)                返回 a 的绝对值</p>
<p>  static float abs(float a)              返回 a 的绝对值</p>
<p>  static double abs(double a)            返回 a 的绝对值</p>
<p>  static int max(int x,int y)            返回 x 和 y 中的最大值</p>
<p>  static double max(double x,double y)   返回 x 和 y 中的最大值</p>
<p>  static long max(long x,long y)         返回 x 和 y 中的最大值</p>
<p>  static float max(float x,float y)      返回 x 和 y 中的最大值</p>
<p>  static int min(int x,int y)            返回 x 和 y 中的最小值</p>
<p>  static long min(long x,long y)         返回 x 和 y 中的最小值</p>
<p>  static double min(double x,double y)   返回 x 和 y 中的最小值</p>
<p>  static float min(float x,float y)      返回 x 和 y 中的最小值</p>
<hr>
<p>public class Test02 { public static void main(String[] args) { System.out.println(&quot;10 和 20 的较大值：&quot; + Math.max(10, 20)); System.out.println(&quot;15.6 和 15 的较小值：&quot; + Math.min(15.6, 15)); System.out.println(&quot;-12 的绝对值：&quot; + Math.abs(-12)); } }</p>
<p><strong>求整运算</strong></p>
<hr>
<p>  方法                            说明</p>
<p>  static double ceil(double a)    返回大于或等于 a 的最小整数</p>
<p>  static double floor(double a)   返回小于或等于 a 的最大整数</p>
<p>  static double rint(double a)    返回最接近 a 的整数值，如果有两个同样接近的整数，则结果取偶数</p>
<p>  static int round(float a)       将参数加上 1/2 后返回与参数最近的整数</p>
<p>  static long round(double a)     将参数加上 1/2 后返回与参数最近的整数，然后强制转换为长整型</p>
<hr>
<p>import java.util.Scanner; public class Test03 { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.outprintln(&quot;请输入一个数字：&quot;); double num = input.nextDouble(); System.out.println(&quot;大于或等于 &quot;+ num +&quot; 的最小整数：&quot; + Math.ceil(num)); System.out.println(&quot;小于或等于 &quot;+ num +&quot; 的最大整数：&quot; + Math.floor(num)); System.out.println(&quot;将 &quot;+ num +&quot; 加上 0.5 之后最接近的整数：&quot; + Math.round(num)); System.out.println(&quot;最接近 &quot;+num+&quot; 的整数：&quot; + Math.rint(num)); } } 执行结果如下： 请输入一个数字： 99.01 大于或等于 99.01 的最小整数：100.0 小于或等于 99.01 的最大整数：99.0 将 99.01 加上 0.5 之后最接近的整数：100 最接近 99.01 的整数：99.0</p>
<p><strong>三角函数运算</strong></p>
<hr>
<p>  方法                                     说明</p>
<p>  static double sin(double a)              返回角的三角正弦值，参数以孤度为单位</p>
<p>  static double cos(double a)              返回角的三角余弦值，参数以孤度为单位</p>
<p>  static double asin(double a)             返回一个值的反正弦值，参数域在 [-1,1]，值域在 [-PI/2,PI/2]</p>
<p>  static double acos(double a)             返回一个值的反余弦值，参数域在 [-1,1]，值域在 [0.0,PI]</p>
<p>  static double tan(double a)              返回角的三角正切值，参数以弧度为单位</p>
<p>  static double atan(double a)             返回一个值的反正切值，值域在 [-PI/2,PI/2]</p>
<p>  static double toDegrees(double angrad)   将用孤度表示的角转换为近似相等的用角度表示的角</p>
<p>  staticdouble toRadians(double angdeg)    将用角度表示的角转换为近似相等的用弧度表示的角</p>
<hr>
<p>在表 3 中，每个方法的参数和返回值都是 double 类型，参数以弧度代替角度来实现，其中 1 度等于 π/180 弧度，因此平角就是 π 弧度。</p>
<p>计算 90 度的正弦值、0 度的余弦值、1 的反正切值、120 度的弧度值 public class Test04 { public static void main(String[] args) { System.out.println{&quot;90 度的正弦值：&quot; + Math.sin(Math.PI/2)); System.out.println(&quot;0 度的余弦值：&quot; + Math.cos(0)); System.out.println(&quot;1 的反正切值：&quot; + Math.atan(l)); System.out.println(&quot;120 度的弧度值：&quot; + Math.toRadians(120.0)); } } 90 度的正弦值：1.0 0 的余弦值：1.0 1 的反正切值：0.7853981633974483 120 度的弧度值：2.0943951023931953</p>
<p>在上述代码中，因为 Math.sin() 中的参数的单位是弧度，而 90 度表示的是角度，因此需要将 90 度转换为弧度，即 Math.PI/180*90，故转换后的弧度为 Math.PI/2，然后调用 Math 类中的 sin() 方法计算其正弦值。</p>
<p><strong>指数运算</strong></p>
<p>指数的运算包括求方根、取对数及其求 n 次方的运算。</p>
<hr>
<p>  方法                                   说明</p>
<p>  static double exp(double a)            返回 e 的 a 次幂</p>
<p>  static double pow(double a,double b)   返回以 a 为底数，以 b 为指数的幂值</p>
<p>  static double sqrt(double a)           返回 a 的平方根</p>
<p>  static double cbrt(double a)           返回 a 的立方根</p>
<p>  static double log(double a)            返回 a 的自然对数，即 lna 的值</p>
<p>  static double log10(double a)          返回以 10 为底 a 的对数</p>
<hr>
<p>例 5</p>
<p>使用 Math 类中的方法实现指数的运算，main() 方法中的代码如下：</p>
<p>public class Test05 { public static void main(String[] args) { System.out.println(&quot;4 的立方值：&quot; + Math.pow(4, 3)); System.out.println(&quot;16 的平方根：&quot; + Math.sqrt(16)); System.out.println(&quot;10 为底 2 的对数：&quot; + Math.log1O(2)); } } 4 的立方值：64.0 16 的平方根：4.0 10 为底 2 的对数：0.3010299956639812</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/Math-%E8%AF%B4%E6%98%8E%EF%BC%9A%E6%96%87%E7%AB%A0%E6%A0%87%E7%AD%BE/" rel="tag">Math#说明：文章标签</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/Optional"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Optional/"
    >Optional</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/Optional/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p>背景: Java 中最常见的异常就是 <code>java.lang.NullPointerException.</code></p>
<p>Optional 类应运而生, <strong>避免代码抛出 NPE 异常</strong></p>
<h3 id="Optional用处"><a href="#Optional用处" class="headerlink" title="Optional用处"></a><strong>Optional用处</strong></h3><ol>
<li>本质上，Optional是一个包含有可选值的包装类，这意味着 Optional 类既可以含有对象也可以为空。</li>
<li>我们要知道，Optional 是 Java 实现函数式编程的强劲一步，并且帮助在范式中实现。但是 Optional 的意义显然不止于此。</li>
<li>任何访问对象方法或属性的调用都可能导致 <code>NullPointerException</code>，在这里，我举个简单的例子来说明一下<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String result = test.getName().getTime().getNum().getAnswer();</span><br></pre></td></tr></table></figure>
1. 在上面的这个代码中，如果我们需要确保不触发异常，就得在访问每一个值之前对其进行明确地检查，就是使用if else对test等值进行判断是否为null，这很容易就变得冗长，难以维护。<blockquote>
<p> 为了简化这个过程，Google公司著名的Guava项目引入了Optional类，Guava通过使用检查空值的方式来防止代码污染，并鼓励程序员写更干净的代码。</p>
</blockquote>
</li>
</ol>
<p>2. Optional实际上是个容器：它可以保存类型T的值，或者仅仅保存null。Optional提供很多有用的方法，这样我们就不用显式进行空值检测。</p>
<h3 id="一、Optional的构造函数"><a href="#一、Optional的构造函数" class="headerlink" title="一、Optional的构造函数"></a><strong>一、Optional的构造函数</strong></h3><p>Optional 的三种构造方式：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Optional.of(obj)，</span><br><span class="line">Optional.ofNullable(obj) ,</span><br><span class="line">明确的 Optional.empty()</span><br></pre></td></tr></table></figure>
<p>**Optional.of(obj)**：<br>它要求传入的 obj 不能是 null 值的, 否则直接报NullPointerException 异常。</p>
<p><strong>Optional.ofNullable(obj)：</strong><br>它以一种智能的，宽容的方式来构造一个 Optional 实例。来者不拒，传 null 进到就得到 Optional.empty()，非 null 就调用 Optional.of(obj).</p>
<p><strong>Optional.empty()：</strong><br>返回一个空的 Optional 对象</p>
<h3 id="二、Optional的常用函数"><a href="#二、Optional的常用函数" class="headerlink" title="二、Optional的常用函数"></a><strong>二、Optional的常用函数</strong></h3><p><strong>of</strong>：<br>为非null的值创建一个Optional。of方法通过工厂方法创建Optional类。需要注意的是，创建对象时传入的参数不能为null。如果传入参数为null，则抛出NullPointerException。因此不经常用。</p>
<p><strong>ofNullable</strong>：<br>为指定的值创建一个Optional，如果指定的值为null，则返回一个空的Optional。</p>
<p><strong>isPresent</strong>：<br>如果值存在返回true，否则返回false。</p>
<p><strong>ifPresent</strong>：<br>如果Optional实例有值则为其调用consumer，否则不做处理</p>
<p><strong>get</strong>：<br>如果Optional有值则将其返回，否则抛出NoSuchElementException。因此也不经常用。</p>
<p><strong>orElse</strong>：<br>如果有值则将其返回，否则返回指定的其它值。</p>
<p><strong>orElseGet</strong>：<br>orElseGet与orElse方法类似，区别在于得到的默认值。orElse方法将传入的字符串作为默认值，orElseGet方法可以接受Supplier接口的实现用来生成默认值</p>
<p><strong>orElseThrow</strong>：<br>如果有值则将其返回，否则抛出supplier接口创建的异常。</p>
<p><strong>filter</strong>：<br>如果有值并且满足断言条件返回包含该值的Optional，否则返回空Optional。</p>
<p><strong>map</strong>：<br>如果有值，则对其执行调用mapping函数得到返回值。如果返回值不为null，则创建包含mapping返回值的Optional作为map方法返回值，否则返回空Optional。</p>
<p><strong>flatMap</strong>：<br>如果有值，为其执行mapping函数返回Optional类型返回值，否则返回空Optional。</p>
<h3 id="三、Optional-应该怎样用"><a href="#三、Optional-应该怎样用" class="headerlink" title="三、Optional 应该怎样用"></a><strong>三、Optional 应该怎样用</strong></h3><ol>
<li>在使用 Optional 的时候需要考虑一些事情，以决定什么时候怎样使用它。重要的一点是 Optional 不是 Serializable。因此，它不应该用作类的字段。</li>
<li>如果你需要序列化的对象包含 Optional 值，Jackson 库支持把 Optional 当作普通对象。也就是说，Jackson 会把空对象看作 null，而有值的对象则把其值看作对应域的值。这个功能在 jackson-modules-java8 项目中。</li>
<li>Optional 主要用作返回类型，在获取到这个类型的实例后，如果它有值，你可以取得这个值，否则可以进行一些替代行为。Optional 类可以将其与流或其它返回 Optional 的方法结合，以构建流畅的API。我们来看一个示例，我们不使用Optional写代码是这样的<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">public String getName(User user)&#123; </span><br><span class="line">    if(user == null)</span><br><span class="line">        return &quot;Unknown&quot;; </span><br><span class="line">    else </span><br><span class="line">        return user.name(); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<ul>
<li>接着我们来改造一下上面的代码，使用Optional来改造，我们先来举一个Optional滥用，没有达到流畅的链式API，反而复杂的例子，如下<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public String getName(User user)&#123; </span><br><span class="line">    Optional&lt;User&gt; u = Optional.ofNullable(user); </span><br><span class="line">    if(!u.isPresent())&#123;</span><br><span class="line">        return &quot;Unknown&quot;; </span><br><span class="line">    else </span><br><span class="line">        return u.get().name(); </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>这样改写非但不简洁，而且其操作还是和第一段代码一样。无非就是用isPresent方法来替代原先user==null。这样的改写并不是Optional正确的用法，我们再来改写一次。<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public String getName(User user)&#123; </span><br><span class="line">    return Optional.ofNullable(user) .map(u -&gt; u.name) .orElse(&quot;Unknown&quot;); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>这样才是正确使用Optional的姿势。那么按照这种思路，我们可以安心的进行链式调用，而不是一层层判断了。当然，我们还可以通过getter方式，对代码进行进一步缩减（前提是User要有getter方法哦），如下<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">String result = Optional             </span><br><span class="line">                    .ofNullable(user)</span><br><span class="line">                    .flatMap(User::getAddress)</span><br><span class="line">                    .flatMap(Address::getCountry)</span><br><span class="line">                    .map(Country::getIsocode)</span><br><span class="line">                    .orElse(&quot;default&quot;);</span><br></pre></td></tr></table></figure></li>
</ul>
<hr>
<p>  <code>of(Object value)    </code>       将非空值设置到Optional实例并返回该实例</p>
<p> <code>ofNullable(Object value) </code> 将值设置到Optional实例中并返回该实例</p>
<p>  <code>get()</code>                      非空则返回Optional内存的值，为空报错NoSuchElementException</p>
<p>  <code>empty()</code>                    返回一个空 Optional实例</p>
<p>  <code>orElse(Object other) </code>      如果当前有值且非空则返回该值，否则返回 other</p>
<p>  <code>isPresent()</code>                如果值存在则方法会返回true，否则返回 false。</p>
<hr>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">Optional.of(2); </span><br><span class="line">System.out.println(Optional.of(4)); //输出：Optional[4]</span><br><span class="line"></span><br><span class="line">System.out.println(Optional.of(4).get()); //输出：4</span><br><span class="line"></span><br><span class="line">System.out.println(Optional.ofNullable(null)); //输出：Optional.empty </span><br><span class="line">System.out.println(Optional.ofNullable(null).get());//报错 </span><br><span class="line">System.out.println(Optional.ofNullable(null).orElse(&quot;2222&quot;)); //输出2222 </span><br><span class="line">System.out.println(Optional.ofNullable(&quot;1111&quot;).orElse(&quot;2222&quot;));//输出1111 </span><br><span class="line">System.out.println(Optional.ofNullable(null).isPresent()); //输出false </span><br><span class="line">System.out.println(Optional.ofNullable(&quot;1111&quot;).isPresent()); //输出true</span><br></pre></td></tr></table></figure> 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/java%E5%9F%BA%E7%A1%80%EF%BC%8COptional/" rel="tag">java基础，Optional</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/System&amp;Object"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/System&Object/"
    >System &amp; Object</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/System&Object/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <h4 id="System中代表程序所在系统，提供了对应的一些系统属性信息，和系统操作。"><a href="#System中代表程序所在系统，提供了对应的一些系统属性信息，和系统操作。" class="headerlink" title="System中代表程序所在系统，提供了对应的一些系统属性信息，和系统操作。"></a><strong>System中代表程序所在系统，提供了对应的一些系统属性信息，和系统操作。</strong></h4><p><code>currentTimeMillis()</code></p>
<p>获取当前系统时间与1970年01月01日00:00点之间的毫秒差值</p>
<p><code>exit(int status)</code></p>
<p>用来结束正在运行的Java程序。参数传入一个数字即可。通常传入0记为正常状态，其他为异常状态</p>
<p><code>gc()</code></p>
<p>用来运行JVM中的垃圾回收器，完成内存中垃圾的清除</p>
<p><code>arraycopy</code></p>
<p>用来实现将源数组部分元素复制到目标数组的指定位置</p>
<p><code>getProperty(String key)</code></p>
<p>用来获取指定键(字符串名称)中所记录的系统属性信息</p>
<hr>
<h3 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h3><p>  <code>方法名</code>                              方法描述</p>
<p> <code> Object()</code>                            默认构造方法</p>
<p> <code> clone()</code>                             创建并返回对象的一个副本</p>
<p>  <code>equals(Object obj)   </code>               指示某个其他对象是否与此对象”相等”</p>
<p>  <code>finalize()  </code>                        对象不存在更多引用时，由对象的垃圾回收器调用</p>
<p>  <code>getClass()</code>                          返回一个对象的运行时类</p>
<p>  <code>hashCode()</code>                          返回该对象的散列码值</p>
<p>  <code>notify()</code>                            唤醒在此对象监视上等待的单个线程</p>
<p>  <code>notifyAll() </code>                        唤醒在此对象监视上等待的所有线程</p>
<p>  <code>toString()</code>                          返回该对象的字符串表示</p>
<p>  <code>wait()</code>                              导致当前的线程等待，直到其他线程调用该线程的唤醒方法</p>
<hr>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/System%EF%BC%8CObject/" rel="tag">System，Object</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
    <article
  id="post-java基础/StringBuffer&amp;StringBuilder"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h2 itemprop="name">
  <a class="article-title" href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/StringBuffer&StringBuilder/"
    >StringBuffer&amp;StringBuilder</a> 
</h2>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/hexo1/2019/08/13/java%E5%9F%BA%E7%A1%80/StringBuffer&StringBuilder/" class="article-date">
  <time datetime="2019-08-13T09:00:00.000Z" itemprop="datePublished">2019-08-13</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/hexo1/categories/java%E5%9F%BA%E7%A1%80/">java基础</a>
  </div>
   
    </div>
      
    <div class="article-entry" itemprop="articleBody">
       
  <p><strong>修改字符串</strong>，需要使用 StringBuffer 和 StringBuilder</p>
<p><strong>StringBuilder类常用方法：</strong></p>
<p>new StringBuiler(String str) <strong>String转为StringBuiler</strong></p>
<p><strong>append</strong>(任意类型)  追加到字符串后面</p>
<p><strong>reverse</strong> 反转字符串</p>
<p><strong>insert</strong>(int index, 任意类型)  在某个index后插入字符串</p>
<p>toString()  <strong>StringBuiler转为String</strong></p>
<p>1. 如何字符串转为数组和列表</p>
<p>2. 切记：对StringBuilder对象进行一次操作，其本身立刻变化，不会创建新对象</p>
<p>看清输出结果</p>
<p>StringBuilder stringBuilder = new StringBuilder(&quot;abc&quot;); stringBuilder.append(stringBuilder.reverse()); //输出：cbacba,</p>
<p>3. 新创建列表不希望add每个元素，可以：</p>
<p>List&lt;String&gt; arrayList = Arrays.aslist(&quot;aa&quot;, &quot;bb&quot;, &quot;cc&quot;);</p>
<p>stringBuilder.append(&quot;a&quot;).append(&quot;b&quot;);</p>
<p><strong>两者区别：</strong></p>
<p>（1）区别1：线程安全</p>
<p>StringBuffer：线程安全，StringBuilder：线程不安全。因为 StringBuffer 的所有公开方法都是 synchronized 修饰的，而 StringBuilder 并没有 StringBuilder 修饰。</p>
<p>（2）区别2：缓冲区</p>
<p>StringBuffer 代码片段：</p>
<p>private transient char[] toStringCache; @Override public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }</p>
<p>StringBuilder 代码片段：</p>
<p>@Override public String toString() { return new String(value, 0, count); }</p>
<p>可以看出，StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。而 StringBuilder 则每次都需要复制一次字符数组，再构造一个字符串。所以，缓存冲这也是对 StringBuffer 的一个优化吧，不过 StringBuffer 的这个toString 方法仍然是同步的。</p>
<p>（3）区别3：性能</p>
<p>既然 StringBuffer 是线程安全的，它的所有公开方法都是同步的，StringBuilder 是没有对方法加锁同步的，所以毫无疑问，StringBuilder 的性能要远大于 StringBuffer。</p>
<p>总结</p>
<p>所以，StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景，如果是单线程场合 StringBuilder 更适合。</p>
<p><strong>为什么修改字符串要用StringBuilder类？</strong></p>
<p><strong>在String类型上修改字符串需要浪费大量内存，且开发效率低</strong></p>
<p>先看一段String类的字符串拼接的代码。</p>
<p>String s = &quot;hello&quot; 会在常量池开辟一个内存空间来存储”hello&quot;。</p>
<p>s += &quot;world&quot;会先在常量池开辟一个内存空间来存储”world&quot;。然后再开辟一个内存空间来存储”helloworld”。</p>
<p>这么以来，001与002就成为了垃圾内存空间了。这么简单的一个操作就产生了两个垃圾内存空间，如果有大量的字符串拼接，将会造成极大的浪费。</p>
<p><strong>StringBuilder</strong></p>
<p>StringBuilder类是一个可变的字符序列。</p>
<p><strong>StringBuffer加上了锁关键字</strong></p>
<p><img src="media/image1.png" alt="clipboard.png">{width=”4.958333333333333in” height=”2.3316218285214347in”}</p>
<p><img src="media/image2.png" alt="clipboard.png">{width=”4.986111111111111in” height=”1.7581091426071742in”}</p>
<p>由此可见，StringBuffer类中的方法都添加了<strong>synchronized关键字</strong>，也就是给这个方法添加了一个 <strong>锁</strong>，用来保证线程安全。</p>
<p><strong>Java9的改进</strong></p>
<p>  Java9改进了字符串（包括String、StringBuffer、StringBuilder）的实现。在Java9以前字符串采用char[]数组来保存字符，因此字符串的每个字符占2字节；而Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符，因此字符串的每个字符只占1字节。所以Java9的字符串更加节省空间，字符串的功能方法也没有受到影响。</p>
<p><strong>构造方法摘要：</strong></p>
<p>StringBuilder()</p>
<p>          构造一个不带任何字符的字符串生成器，其初始容量为 16 个字符。</p>
<p>StringBuilder(CharSequence seq)</p>
<p>          构造一个字符串生成器，它包含与指定的 CharSequence 相同的字符。</p>
<p>StringBuilder(int capacity)</p>
<p>          构造一个不带任何字符的字符串生成器，参数是int类型，表初始容量。</p>
<p><strong>StringBuilder(String str)</strong></p>
<p>**      **    构造一个字符串生成器<strong>，并初始化为指定的字符串。</strong></p>
<p> </p>
<p><img src="media/image3.png" alt="clipboard.png">{width=”5.263888888888889in” height=”4.197992125984252in”}</p>
<p><strong>2. StringBuilder与String的相互转换：</strong></p>
<p>public class String12 { public static void main(String[] args){ StringBuilder sb = new StringBuilder(); sb.append(&quot;abc&quot;).append(&quot;efg&quot;); <strong>String s = sb.toString();</strong> String s = &quot;hello&quot;; StringBuilder sb = new StringBuilder(s); s.reverse(); s1.delete(1, 3); } }</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <footer class="article-footer">
       
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/hexo1/tags/StringBuffer-StringBuilder/" rel="tag">StringBuffer/StringBuilder</a></li></ul>

    </footer>
  </div>

    
 
    
</article>

    
  </article>
  

  
  <nav class="page-nav">
    
    <span class="page-number current">1</span><a class="page-number" href="/hexo1/page/2/">2</a><a class="extend next" rel="next" href="/hexo1/page/2/">下一页</a>
  </nav>
  
</section>
</div>

      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2020-2021
        <i class="ri-heart-fill heart_icon"></i> Zhang Shuo
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src=''></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/hexo1/"><img src="/hexo1/images/ayer-side.svg" alt="Zhang Shuo&#39;s Studio"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/tags/%E6%97%85%E8%A1%8C/">旅行</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://shenyu-vip.lofter.com">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/hexo1/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/hexo1/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/hexo1/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/hexo1/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/hexo1/js/jquery-3.6.0.min.js"></script>
 
<script src="/hexo1/js/lazyload.min.js"></script>

<!-- Tocbot -->

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/hexo1/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/hexo1/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/hexo1/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    <div id="music">
    
    
    
    <iframe frameborder="no" border="1" marginwidth="0" marginheight="0" width="200" height="52"
        src="//music.163.com/outchain/player?type=2&id=22707008&auto=1&height=32"></iframe>
</div>

<style>
    #music {
        position: fixed;
        right: 15px;
        bottom: 0;
        z-index: 998;
    }
</style>
    
    

  </div>
</body>

</html>